#include "trigger.hpp"
#include "money-tree.hpp"
#include "params.hpp"
#include "tjm_action.hpp"

namespace Trade {

void
Trigger::set_call_back(trigger_call_back tcb) noexcept
{
  trigger_cb = tcb;
}

void
Trigger::set_error(const price_t hp_err, const int tm_err) noexcept
{
  this->hp_err = hp_err;
  this->tm_err = tm_err;
}

void
Trigger::set_price_trigger(const dir_t d,
                           const price_t hpx,
                           const TRIGGER_EVENT_TYPE why,
                           const Params* vo,
                           const MoneyTree* mt,
                           const DATAPTR userdata) noexcept
{
  if (hpx < 0)
    return;

  dir = d;
  this->hpx = hpx;
  etype = why;
  triggered = false;
  reached_hpx = 0;
  this->userdata = userdata;
  ttype = TRIGGER_TYPE::SPACE;
  if (vo)
    memcpy((void*)(&params), vo, sizeof(Params));

  do_something(); // set the params of MoneyTree
}

void
Trigger::set_timer_trigger(const time_t tm,
                           const TRIGGER_EVENT_TYPE why,
                           const Params* vo,
                           const MoneyTree* mt,
                           const DATAPTR userdata) noexcept
{
  this->tm = tm;
  triggered = false;
  reached_tm = 0;
  ttype = TRIGGER_TYPE::TIME;
  this->userdata = userdata;
  if (vo)
    memcpy((void*)(&params), vo, sizeof(Params));

  do_something(); // set the params of MoneyTree
}

TRIGGER_TYPE
Trigger::pull_trigger(const price_t hpx, const time_t tm) noexcept
{
  if (triggered)
    return TRIGGER_TYPE::NONE;

  TRIGGER_TYPE tt = TRIGGER_TYPE::NONE;
  bool td = false;
  if (ttype == TRIGGER_TYPE::SPACE) {
    if (dir && (std::abs(hpx - this->hpx) < hp_err)) {
      td = triggered = true;
      tt = TRIGGER_TYPE::SPACE;
    }
  } else if (ttype == TRIGGER_TYPE::TIME) {
    if (tm && tm - this->tm < tm_err) {
      td = triggered = true;
      tt = TRIGGER_TYPE::TIME;
    }
  }

  if (td) {
    reached_hpx = hpx;
    if (trigger_cb)
      trigger_cb(this);
    clear();
  }

  return tt;
}

TRIGGER_TYPE
Trigger::is_triggered() const noexcept
{
  if (triggered)
    return ttype;
  else
    return TRIGGER_TYPE::NONE;
}

void
Trigger::clear() noexcept
{
  auto tcb = trigger_cb;
  const auto err_hp = hp_err;
  const auto err_tm = tm_err;
  memset((void*)(this), 0, sizeof(Trigger));
  tm_err = err_tm;
  hp_err = err_hp;
  trigger_cb = tcb;
}

void
TriggerManager::set_price_trigger(const dir_t d,
                                  const price_t hpx,
                                  const TRIGGER_EVENT_TYPE why,
                                  const Params* vo,
                                  const MoneyTree* mt,
                                  const DATAPTR userdata) noexcept
{
  auto t = get_trigger(why, TRIGGER_TYPE::SPACE);
  if (t)
    t->set_price_trigger(d, hpx, why, vo, mt, userdata);
}
void
TriggerManager::set_timer_trigger(const time_t tm,
                                  const TRIGGER_EVENT_TYPE why,
                                  const Params* vo,
                                  const MoneyTree* mt,
                                  const DATAPTR userdata) noexcept
{
  auto t = get_trigger(why, TRIGGER_TYPE::TIME);
  if (t)
    t->set_timer_trigger(tm, why, vo, mt, userdata);
}

int
TriggerManager::pull_trigger(const price_t hpx, const time_t tm) noexcept
{
  int amount = 0;
  memset((void*)(ta_triggered), 0, sizeof(ta_triggered));
  for (int i = 0; i < int(TRIGGER_EVENT_TYPE::MAX_EVENT_CODE) * 2; ++i) {
    if (ta[i].pull_trigger(hpx, tm) != TRIGGER_TYPE::NONE) {
      ta_triggered[i] = ta + i;
      ++amount;
    }
  }
  return amount;
}

TRIGGER_TYPE
TriggerManager::is_triggered(const TRIGGER_EVENT_TYPE why,
                             const TRIGGER_TYPE ttype) noexcept
{
  const auto t = get_trigger(why, ttype);
  if (t) {
    return t->is_triggered();
  }
  return TRIGGER_TYPE::NONE;
}

Trigger*
TriggerManager::get_trigger(const TRIGGER_EVENT_TYPE why,
                            const TRIGGER_TYPE ttype) noexcept
{
  if (!(uint32_t)(why) || !(uint32_t)(ttype) ||
      why == TRIGGER_EVENT_TYPE::MAX_EVENT_CODE)
    return nullptr;

  return ta + (int)(why) +
         (int(ttype) - 1) * int(TRIGGER_EVENT_TYPE::MAX_EVENT_CODE);
}

void
TriggerManager::set_mark_price_up(const price_t hpx,
                                  const Params* vo,
                                  const MoneyTree* mt,
                                  const DATAPTR userdata) noexcept
{
  auto t = get_trigger(TRIGGER_EVENT_TYPE::MARK_PRICE_UP, TRIGGER_TYPE::SPACE);
  if (t)
    t->set_price_trigger(
      1, hpx, TRIGGER_EVENT_TYPE::MARK_PRICE_UP, vo, mt, userdata);
}
void
TriggerManager::set_mark_price_dn(const price_t hpx,
                                  const Params* vo,
                                  const MoneyTree* mt,
                                  const DATAPTR userdata) noexcept
{
  auto t = get_trigger(TRIGGER_EVENT_TYPE::MARK_PRICE_DN, TRIGGER_TYPE::SPACE);
  if (t)
    t->set_price_trigger(
      -1, hpx, TRIGGER_EVENT_TYPE::MARK_PRICE_UP, vo, mt, userdata);
}

void
TriggerManager::unset_mark_price_up() noexcept
{
  auto t = get_trigger(TRIGGER_EVENT_TYPE::MARK_PRICE_UP, TRIGGER_TYPE::SPACE);
  if (t && t->dir)
    t->clear();
}

void
TriggerManager::unset_mark_price_dn() noexcept
{
  auto t = get_trigger(TRIGGER_EVENT_TYPE::MARK_PRICE_DN, TRIGGER_TYPE::SPACE);
  if (t && t->dir)
    t->clear();
}

void
TriggerManager::set_timer(const time_t tm,
                          const Params* vo,
                          const MoneyTree* mt,
                          const DATAPTR userdata) noexcept
{
  auto t = get_trigger(TRIGGER_EVENT_TYPE::MARK_TIMER, TRIGGER_TYPE::SPACE);
  if (t)
    t->set_timer_trigger(tm, TRIGGER_EVENT_TYPE::MARK_TIMER, vo, mt, userdata);
}

void
TriggerManager::unset_timer() noexcept
{
  auto t = get_trigger(TRIGGER_EVENT_TYPE::MARK_TIMER, TRIGGER_TYPE::SPACE);
  if (t && t->tm)
    t->clear();
}

void
TriggerManager::set_open_price(const dir_t d,
                               const price_t good_to_open,
                               const Params* vo,
                               const MoneyTree* mt,
                               const DATAPTR userdata) noexcept
{
  auto t = get_trigger(TRIGGER_EVENT_TYPE::OPEN, TRIGGER_TYPE::SPACE);
  if (t) {
    t->set_price_trigger(
      -d, good_to_open, TRIGGER_EVENT_TYPE::OPEN, vo, mt, userdata);
  }
}

void
TriggerManager::set_close_price(const dir_t d,
                                const price_t good_to_win,
                                const Params* vo,
                                const MoneyTree* mt,
                                const DATAPTR userdata) noexcept
{
  auto t = get_trigger(TRIGGER_EVENT_TYPE::CLOSE, TRIGGER_TYPE::SPACE);
  if (t) {
    t->set_price_trigger(
      -d, good_to_win, TRIGGER_EVENT_TYPE::CLOSE, vo, mt, userdata);
  }
}

void
TriggerManager::set_close_stop_loss(const dir_t d,
                                    const price_t stop_loss,
                                    const Params* vo,
                                    const MoneyTree* mt,
                                    const DATAPTR userdata) noexcept
{
  auto t =
    get_trigger(TRIGGER_EVENT_TYPE::CLOSE_STOP_LOSS, TRIGGER_TYPE::SPACE);
  if (t) {
    t->set_price_trigger(
      -d, stop_loss, TRIGGER_EVENT_TYPE::CLOSE_STOP_LOSS, vo, mt, userdata);
  }
}

void
TriggerManager::set_close_stop_win(const dir_t d,
                                   const price_t max_win,
                                   const Params* vo,
                                   const MoneyTree* mt,
                                   const DATAPTR userdata) noexcept
{
  auto t = get_trigger(TRIGGER_EVENT_TYPE::CLOSE_STOP_WIN, TRIGGER_TYPE::SPACE);
  if (t) {
    t->set_price_trigger(
      -d, max_win, TRIGGER_EVENT_TYPE::CLOSE_STOP_WIN, vo, mt, userdata);
  }
}

void
TriggerManager::set_close_min_win(const dir_t d,
                                  const price_t min_win,
                                  const Params* vo,
                                  const MoneyTree* mt,
                                  const DATAPTR userdata) noexcept
{
  auto t = get_trigger(TRIGGER_EVENT_TYPE::CLOSE_MIN_WIN, TRIGGER_TYPE::SPACE);
  if (t) {
    t->set_price_trigger(
      -d, min_win, TRIGGER_EVENT_TYPE::CLOSE_MIN_WIN, vo, mt, userdata);
  }
}

void
TriggerManager::unset_all() noexcept
{
   for (int i = 0; i < int(TRIGGER_EVENT_TYPE::MAX_EVENT_CODE) * 2; ++i) {
    ta[i].clear();
  }
}

void
TriggerManager::unset_open_price() noexcept
{
  auto t = get_trigger(TRIGGER_EVENT_TYPE::OPEN, TRIGGER_TYPE::SPACE);
  if (t && t->dir) {
    t->clear();
  }
}

void
TriggerManager::unset_close_price() noexcept
{
  auto t = get_trigger(TRIGGER_EVENT_TYPE::CLOSE, TRIGGER_TYPE::SPACE);
  if (t && t->dir) {
    t->clear();
  }
}

void
TriggerManager::unset_close_stop_loss() noexcept
{
  auto t =
    get_trigger(TRIGGER_EVENT_TYPE::CLOSE_STOP_LOSS, TRIGGER_TYPE::SPACE);
  if (t && t->dir) {
    t->clear();
  }
}

void
TriggerManager::unset_close_stop_win() noexcept
{
  auto t = get_trigger(TRIGGER_EVENT_TYPE::CLOSE_MIN_WIN, TRIGGER_TYPE::SPACE);
  if (t && t->dir) {
    t->clear();
  }
}

void
TriggerManager::unset_close_min_win() noexcept
{
  auto t = get_trigger(TRIGGER_EVENT_TYPE::CLOSE_MIN_WIN, TRIGGER_TYPE::SPACE);
  if (t && t->dir) {
    t->clear();
  }
}

void
TriggerManager::unset_all_close() noexcept
{
  unset_close_price();
  unset_close_min_win();
  unset_close_stop_loss();
  unset_close_stop_win();
}

void
TriggerManager::print_close_triggers(const price_t hp_now) const noexcept
{
}

price_t
TriggerManager::get_close_price_stop_loss() noexcept
{
  auto t =
    get_trigger(TRIGGER_EVENT_TYPE::CLOSE_STOP_LOSS, TRIGGER_TYPE::SPACE);
  if (t)
    return t->hpx;
  return (-1);
}

price_t
TriggerManager::get_close_price_stop_win() noexcept
{
  auto t = get_trigger(TRIGGER_EVENT_TYPE::CLOSE_STOP_WIN, TRIGGER_TYPE::SPACE);
  if (t)
    return t->hpx;
  return (-1);
}

price_t
TriggerManager::get_close_price_min_win() noexcept
{
  auto t = get_trigger(TRIGGER_EVENT_TYPE::CLOSE_MIN_WIN, TRIGGER_TYPE::SPACE);
  if (t)
    return t->hpx;
  return (-1);
}

price_t
TriggerManager::get_close_price() noexcept
{
  auto t = get_trigger(TRIGGER_EVENT_TYPE::CLOSE, TRIGGER_TYPE::SPACE);
  if (t)
    return t->hpx;
  return (-1);
}

} // namespace Trade